Skip to content

S12-01 新特性-Lambda ​

[TOC]

概述 ​

Java 8 引入的 Lambda 表达式本质上是一个匿名函数,是函数式接口的具体实现。它允许将一段执行行为作为参数在方法间传递,彻底解决了传统匿名内部类书写繁琐、样板代码冗长的问题。

语法规则 ​

基础结构 ​

Lambda 表达式由三部分构成:形参列表、箭头符号 -> 和代码主体。

java
// 传统无参 Runnable 接口逻辑实现
Runnable task = () -> System.out.println("Task executed");
task.run();
java
// 接收两个参数并返回计算结果
Comparator<Integer> comp = (x, y) -> Integer.compare(x, y);
int result = comp.compare(10, 20);

简写规则 ​

  1. 类型推断:参数类型由编译器依据上下文自动推导,参数列表中的类型声明通常可以全部省略。

  2. 单参数去括号:若形参只有一个,包裹形参的圆括号 () 可以省略;零个或多个参数则不可省略。

  3. 单行主体精简:当执行体只包含一行表达式时,包裹主体的大括号 {} 以及 return 关键字必须同时省略;若保留大括号,有返回值时则必须显式声明 return。

函数式接口 ​

概念定义 ​

Lambda 表达式无法脱离类型独立存在,其目标类型必须是函数式接口(Functional Interface)。函数式接口是指有且仅有一个未实现的抽象方法的接口。

接口内部允许包含任意数量的静态方法(static)、默认方法(default)以及继承自 java.lang.Object 的公共方法,这些都不会破坏其作为函数式接口的判定。

声明规范 ​

通过 @FunctionalInterface 注解显式标注接口,能够使编译器在接口不满足“单一抽象方法”条件时产生编译错误。

java
// 声明只包含一个抽象方法的计算契约接口
@FunctionalInterface
public interface MathOperation {
  int calculate(int a, int b);
}

内置核心接口 ​

Java 在 java.util.function 包中预置了大量开箱即用的函数式接口,其中最核心的类型分为四类:

消费型接口 ​

接收一个参数并对其进行消费操作,不产生返回值。抽象方法为 void accept(T t)。

java
// 接收输入数据并打印,无返回值
Consumer<String> consumer = msg -> System.out.println("Message: " + msg);
consumer.accept("Hello Lambda");

供给型接口 ​

不接收任何参数,直接生产并返回指定类型的数据。抽象方法为 T get()。

java
// 不接受输入,直接生成随机数值
Supplier<Double> randomSupplier = () -> Math.random();
Double val = randomSupplier.get();

函数型接口 ​

接收一个参数,经过内部运算或转换逻辑后,返回另一个类型的结果。抽象方法为 R apply(T t)。

java
// 接收字符串输入并转换为其长度数值
Function<String, Integer> lengthFunc = str -> str.length();
Integer len = lengthFunc.apply("Lambda");

断定型接口 ​

接收一个参数,依据特定条件进行判定并返回布尔值。抽象方法为 boolean test(T t)。

java
// 检验传入的文本是否为空字符串
Predicate<String> emptyCheck = str -> str.isEmpty();
boolean isEmpty = emptyCheck.test("");

变量捕获 ​

事实不可变原则 ​

Lambda 内部可以读取外层作用域中的局部变量,但该变量必须是 final 或事实上的 final(Effectively Final,即在初始化后从未被二次赋值)。

java
// 捕获外部局部变量,该变量不可被重新赋值
int factor = 2;
Runnable r = () -> System.out.println(factor);
r.run();

底层原理分析 ​

方法中的局部变量分配在栈内存,随着方法调用结束栈帧会被销毁;而 Lambda 表达式所创建的对象存活在堆内存中。为了保证执行安全性,Java 并非直接引用该外部变量,而是将变量的值在创建时拷贝了一份副本存入堆中的 Lambda 实例。强制该变量不可修改,消除了多线程环境下的数据竞态与可见性不一致。

方法引用 ​

当 Lambda 表达式的方法体仅仅是调用一个已存在的方法时,可以使用双冒号 :: 语法进一步简化。

静态方法引用 ​

语法为 ClassName::staticMethod。

java
// 直接引用包装类的静态解析方法
Function<String, Integer> parser = Integer::parseInt;
Integer num = parser.apply("123");

实例方法引用 ​

分为两种使用场景:

  1. 引用特定对象的实例方法:instance::method

  2. 引用特定类型的任意对象实例方法:ClassName::method

    java
    // 引用已有对象 System.out 的成员方法
    Consumer<String> printer = System.out::println;
    printer.accept("Log entry");

构造器引用 ​

语法为 ClassName::new,依据上下文函数式接口的入参形态,自动匹配对应的构造函数。

java
// 引用具体集合类的无参构造器
Supplier<List<String>> listFactory = ArrayList::new;
List<String> list = listFactory.get();

数据处理集成 ​

声明式编程范式 ​

Lambda 最常见的进阶应用是配合 Stream API 构建声明式的数据处理管道。这种方式将对集合的“如何遍历”交由底层实现,开发者只需关注“做什么”。

管道操作对比 ​

这种声明式流处理在逻辑意图上与 SQL 保持一致:

sql
SELECT department, AVG(salary)
  FROM employee
  GROUP BY department;

在 Java 中配合方法引用与 Lambda 即可写出等效的集合聚合管道:

java
// 对应 SQL 聚合逻辑的数据流处理管道
Map<String, Double> avgSalary = employees.stream()
  .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.averagingDouble(Employee::getSalary)));

本篇涵盖了 Lambda 从基本语法到实际开发的核心用法。若需要进一步深入其运行本质,下一步可以探讨 JVM 底层原理,例如 invokedynamic 指令与 LambdaMetafactory 是如何在运行时动态生成调用点与匿名类的。